Начало работы
с Visual Studio.Net
Итак, вы успешно
преодолели все трудности установки Microsoft Visual Studio. Net 7.0 (если они
были, а они в изобилии присутствовали в бета-версии продукта, с которой я имел
дело в момент написания книги) и готовы покорить определенные высоты с помощью
вашей неудержимой фантазии программиста и возможностей студии. Инструменты Studio.Net,
несомненно, помогут воплотить ваши идеи в реальные проекты, которые теперь принято
называть решениями (solutions) — термин, обозначающий новую концепцию
логического хранилища проектов.
Если вы имеете
опыт работы в среде Microsoft Visual Studio 6.0, то, открыв Studio.Net, вы сразу
отметите значительные изменения в интерфейсе. Общий облик вызывает ассоциации
с пультом управления летательного аппарата или какого-то другого сложного технического
объекта. Задача одна — в небольшом пространстве разместить множество инструментов
контроля и управления за состоянием объекта. Но в отличие от осязаемого пульта
управления самолетом ваш иллюзорный пульт на экране может динамично изменяться,
отчасти благодаря сравнительно новым элементам управления — tabbed windows
— окнам с вкладками. Открыв Studio.Net, вы увидите такое окно на самом видном
месте. Это окно самое большое по площади. В начальный момент оно имеет только
одну страницу (page), открываемую с помощью вкладки (tab) VS Home
Page. Далее в этой группе будут появляться другие вкладки, позволяющие открывать
другие страницы составного окна. В случае если вы «потеряете» начальное окно,
то его можно вернуть на свое место, дав команду View > Web Browser. Вот другой
способ сделать это:
При поиске
кнопок используйте всплывающие подсказки (tooltips).
Обозревая
окна Studio.Net, отметьте усовершенствования косметического характера: пункты
меню теперь имеют значки, изменились цвета элементов интерфейса в разных состояниях,
нарушив тем самым рекомендации Microsoft по созданию UI-элементов (User Interface).
Visual
C++, Microsoft Developer Network (MSDN), fj, Visual Basic и другие компоненты
составляют интегрированную среду разработки приложений — Visual Studio Integrated
Development Environment (IDE). Совместное использование одной и той же среды
имеет очевидные преимущества, так как в процессе разработки приложений на разных
языках можно пользоваться одними и теми же или сходными инструментами Studio.Net:
Web Browser, Command Window, Tabbed Documents, редакторами кодов, HTML-страниц,XML-схем
и редакторами ресурсов.
Рис.
1.1. Общий вид Studio.Net
Сеанс работы
в Studio.Net начинается с открытия существующего или создания нового решения
(solution). В дальнейшем вместо термина решение я иногда буду
использовать термин рабочее пространство, так как буквальный перевод
— решение — не всегда точен. Файлы с расширением sin используются IDE (Integrated
Development Environment) для хранения настроек и начальных установок конкретных
решений. Концепция решений помогает объединить проекты и другие элементы
в одном рабочем пространстве. Множество файлов разного типа, в рамках одного
решения составляют приложение (application) Visual Studio.Net 7.0. Рабочее
пространство может содержать несколько проектов, быть пустым или содержать файлы,
которые имеют смысл и вне контекста решений. В любом случае, вы должны
начинать работу в студии с открытия существующего или создания нового рабочего
пространства.
Проект
как часть решения состоит из отдельных компонентов, например файлов,
описывающих форму окна или шаблон диалога (re-файл), файлов с исходными
кодами программных модулей (.срр, .cs) и/или файлов, представляющих собой описание
запроса к базе данных (database script), HTML-документов и, т. д. Настройки
проектов хранятся в специальных файлах проектов. Они могут иметь разные расширения,
так как в одном пространстве можно объединять проекты совершенно разных типов.
Например, проект MFC-приложения хранит свои установки в файле с расширением
vcproj, а файл проекта, реализованного на языке
С#, имеет
расширение csproj. Такой файл является читаемым, его можно открыть вне рамок
Studio.Net (например, с помощью Notepad) и увидеть описание установок проекта
на еще одном из «секретных» языков. Например, проект типа MFC Application с
именем MyProj содержит файл MyProj.vcproj, начальный фрагмент которого мы приведем
здесь:
<?xml version="1.0"?>
<VisualStudioProject ProjectType="Visual C++" Version="7.00" Name="MyProj" Keyword="mfc">
<Build>
<Settings>
<Platform Name="Win32"/>
<Configuration
Name="Debug|Win32"
InterraediateDirectory="Debug"
OutputDirectory="Debug"
ConfigurationType="l"
UseOfMFC="2"
CharacterSet="2">
<Tool Name="VCBscMakeTool"/>
<Tool
Name="VCCLCorapilerTool"
Optimization="0"
Нет необходимости
углубляться в анализ языка описания проекта. Поверхностного взгляда достаточно,
чтобы понять, что мы имеем дело с последовательностью <предложений>, описывающих
тип проекта, настройки и перечень инструментов Studio.Net для его обработки.
То же самое можно сказать про sin-файл. Он читаем, и если открыть его
в текстовом режиме, то можно увидеть предложения некоего служебного языка, описывающие
состав и настройки рабочего пространства.
При создании
нового проекта Studio.Net автоматически создает рабочее пространство и помещает
в него этот проект. Вот перечень шагов для создания нового проекта и нового
рабочего пространства (solution), его содержащего.
Рис.
1.2. Окно диалога New Project
Рис.
1.3. Окно мастера MFC Application Wizard
Мы отложим
разговор о различных типах шаблонов (стартовых заготовках) MFC-приложений в
предположении, что читатель имеет представление о них по опыту работы в Visual
Studio б.0. Если это не так, то все равно не прерывайте процесс чтения и исследования
Studio.Net. Примиритесь, временно, с дискомфортом недопонимания. Итак, Application
Wizard потрудился и создал стартовую заготовку нового Windows-приложения, которое
поддерживает многодокументный интерфейс (MDI). Вы можете немедленно его
запустить, дав команду Debug > Start Without Debugging и согласившись
с сообщением о том, что конфигурация проекта устарела (подразумевается, ехе-файл
либо отсутствует, либо старше, чем какой-либо из исходных файлов проекта).
После этого вы имеете возможность наблюдать за процессом компиляции и компоновки
в окне Output, которое, скорее всего, появится внизу главного окна Studio.Net.
Далее вы увидите окно нового приложения My, поддерживающего стандарт MDI. Опробуйте
команды File > New и все команды меню Window этого приложения, следя
за заголовками новых дочерних окоп. Закройте стартовое приложение и сосредоточьте
внимание на окне Studio.Net с заголовком Solution Explorer, которое, скорее
всего, расположено справа от окна VS Home Page.
Неуверенность
относительно местоположения окон объясняется тем, что окна Studio.Net проявляют
удивительную подвижность. При желании вы можете разместить их в разных группах
tabbed-окон или сделать их свободными (floating), причаливаемыми (docable) или
скрыть (hide). Поэкспериментируйте с командами (Docable, Hide, Floating, Auto
Hide контекстного меню, которое появляется при щелчке правой клавишей мыши над
заголовками окон.
Опробуйте
также команды меню Window. Например, выберите произвольное окно и дайте команду
Window > Docable. Отыщите выбранное окно, затем повторите ту же команду и
вновь отыщите окно. При работе с кодами в окне редактора наиболее удобным режимом
для вспомогательных окон представляется Auto Hide. Studio.Net позволяет очень
гибко управлять местоположением своих окон. Отметьте, что вышеупомянутое контекстное
меню динамически изменяется в зависимости от текущего состава группы tabbed-окон.
В нем появляются другие команды, которые расширяют ваши возможности по управлению
интерфейсом Studio.Net. Окно Solution Explorer дает возможность управлять проектами
и файлами проектов. Оно является аналогом окна File View в Visual Studio 6 и
теперь по умолчанию входит в группу окон, составляющих блок очень полезных страниц
(pages), которыми вы будете часто пользоваться. Сейчас активизируйте
страницу Class View из этого блока, для того чтобы увидеть состав классов библиотеки
MFC, использованный в новом приложении. При поиске вкладки Class View в блоке
используйте всплывающие подсказки. Раскройте дерево классов. Отметьте, что теперь
кроме шести классов(CAboutDlg, CChildFrame, CMainFrame, CMyApp, CMyDoc, CMyView)
в дерево входят
и другие элементы, также являющиеся логическими компонентами MFC-приложения.
Это глобальные функции и переменные (Global Functions and Variables), макроподстановки
И константы (Macros and Constants).
Рис.
1.4. Представление классов в окне Class View
Раскройте
элемент дерева классов под именем CMyView. Класс CMyView происходит от MFC-класса
cview и дает вам возможность управлять обликами (views) документов в
рамках модели программирования архитектуры «документ — представление», считающейся
стандартной технологией разработки MFC-приложений. О ней достаточно много сказано.
(См., например, Круглински Д. Основы Visual C++, М: «Русская редакция»,
1997; Черносвитов A. Visual C++ и MFC, СПб.: «Питер», 2000.) Здесь мы
также будем рассматривать особенности технологии, по позднее.
А пока попробуем изменить коды нашего приложения так, чтобы оно умело отображать
данные документа. Выполнив двойной щелчок над элементом дерева OnDraw (CDC *pDC),
вы увидите новое окно-страницу, управляемое вкладкой MyView.cpp. Так именуется
файл реализации (implementation file) класса CMyView. Курсор должен находиться
на теле метода перерисовки:
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
}
Здесь вместо
подсказки // TODO: мы должны вставить код, отображающий данные документа. Функция
OnDraw(CDC *pDC) входит в состав класса CMyView, являсь методом этого класса,
и вызывается каркасом приложения в тех случаях, когда необходимо перерисовать
окно, обслуживаемое классом CMyView.
Каркасом
приложения (Application Framework) называется совокупность классов и других
структур библиотеки MFC, которые присутствуют в вашем приложении неявно. Дело
в том, что классы вашего приложения произведены (с помощью механизма наследования
ООП) от классов MFC. Данные и методы этих классов компилятор включил в исполняемый
модуль, и они работают на вас. (Данные используются, методы вызываются.) Но
вы можете и не знать об этом.
Представлением
документа называется клиентская область одного из окон-рамок, обслуживаемых
классом CChildFrame и живущих внутри главного окна приложения. В MDI приложении
их может быть много. Это те окна, которые можно видеть по очереди, все сразу,
каскадом или рядом, не перекрывая друг друга (Cascade или Tile).
Перед тем
как начать отображение данных, надо эти данные создать или добыть из класса,
обслуживающего документ. В соответствии с концепцией архитектуры «документ —
представление» все стратегические данные приложения должны храниться в классе
документа, то есть в классе CMyDoc. Метод GetDocument (он вызывается в заготовке
OnDraw) класса CMyView позволяет добыть указатель на объект класса CMyDoc, который
управляет активным в данный момент документом.
Макроподстановка
ASSERT_VALID в отладочной (Debug) версии проекта проверяет на осмысленность
полученный указатель и дает сообщение об ошибке, в случае когда указатель равен
нулю или в действительности не является адресом объекта класса, производного
от класса CObject. Если вы просмотрите иерархию классов MFC, то увидите, что
CObject является отцом-прародителем всех классов, потомки которых использованы
в нашем приложении.
Итак, имея адрес документа, мы можем начинать отображение его данных в окне представления. В системе, поддерживающей графический интерфейс пользователя, все данные не просто выводятся на экран, они скорее «рисуются» в контексте устройства, связанном с окном. Подсистема Windows GDI (Graphics Device Interface) дает вам набор средств для рисования, среди которых одним из главных является контекст устройства, управляемый классом CDC (Device Context). Указатель на используемый системой в данный момент объект класса CDC мы получили в качестве параметра функции OnDraw.
Концепция такова: рисование производится в специальной области памяти, управляемой этим классом, с помощью графических примитивов (точек, прямоугольников и т. д.) и логической системы координат. Далее Windows производит преобразование логических координат примитивов в систему физических или аппаратных (device) координат. Идея в том, что программист отображает данные в контексте устройства, не задумываясь о том, где они будут реально воспроизведены (на экране хорошего или плохого монитора, на принтере или на графопостроителе). Программист стремится наиболее точно отобразить данные документа в произвольно выбранной им логической системе координат, а система с помощью драйверов устройств стремится наиболее точно преобразовать все точки рисунка в физическую или аппаратную, то есть связанную с конкретным устройством, систему координат.
Зададимся целью нарисовать в логической системе координат плоский многоугольник (Polygon), координаты точек которого будем хранить в динамической структуре данных. Специалисты советуют в таких случаях пользоваться одним из множества шаблонов, реализующих поведение фундаментальных структур данных и присутствующих в рамках STL (Standard Template Library). Библиотека шаблонов STL доступна на любой платформе, так как является стандартом. Она станет доступной и нам, если мы подключим необходимый файл заголовков. Для хранения точек многоугольника мы выберем шаблон стандартного контейнера, который называется vector. Это динамическая структура данных, которая ведет себя как «умный» массив элементов произвольного типа. Любой контейнер удобно представлять себе в виде резиновой сумки с одинаковыми объектами любой природы, которая почти всегда полна и в которую всегда можно положить еще разумное количество объектов того же типа. Это возможно, потому что она растягивается, то есть способна динамически (на этапе выполнения) изменять свои размеры как в сторону увеличения, так и в сторону уменьшения.
Подробнее о контейнерах будет сказано позже, а сейчас надо решить, что должно в нем храниться. Так как многоугольник определяется координатами точек, то контейнер целесообразно «скроить» (по шаблону vector) так, чтобы в нем можно было хранить объекты класса CPoint. Этот класс является вспомогательным в MFC (не происходит от CObject). Найдите этот класс в иерархии классов библиотеки MFC. Для этого:
Класс CPoint находится в правой части карты под заголовком Simple Value Types. После этого отыщите классы: CObject, CDocument, cview, cwnd, которые так или иначе присутствуют в каркасе нашего приложения. Закройте окна Index, Index Results и Hierarchy Chart.
Теперь вы знаете, что CPoint содержит внутри себя две целые координаты (х, у) произвольной точки и множество полезных методов для управления точкой. Итак, мы решили хранить точки многоугольника (объекты класса CPoint) в контейнере, скроенном по шаблону vector<CPoint>. Параметр шаблона (в угловых скобках) указывает тип объектов, которые будут храниться в контейнере. Воспользуемся контекстным меню, возникающим при правом щелчке мыши (right-click) на имени класса CMyDoc в окне Class View. В этом меню:
Рис. 1.5. Окно мастера Add Variable
Просмотрите описание класса CMyDoc, дважды щелкнув на имени класса в окне Class View. В конце файла вы должны увидеть строку
vector<CPoint> m Points;
Теперь просмотрите тело конструктора класса. Для этого раскройте элемент дерева CMyDoc и дважды щелкните на имени конструктора CMyDoc (void). Вы должны увидеть такой заголовок и тело конструктора:
CMyDoc::CMyDoc()
: m Points (0)
{
}
Обратите внимание
на инициализатор m_Points (0), который был автоматически вставлен мастером Add
Variable. Инициализатор вызывает один из конструкторов шаблона классов vector
и сообщает ему, что перед тем, как создать объект класса CMyDoc, надо создать
объект m_Points типа vector и задать ему нулевой размер. Нам не нужен этот инициализатор,
так как мы собираемся записать в контейнер m_Points координаты тестового многоугольника.
Тело конструктора документа пока пусто. Наполним его кодами, вычисляющими точки
многоугольника, так чтобы он имел вид пятиконечной звезды. Звезда удобна тем,
что позволяет продемонстрировать способы закраски самопересекающихся многоугольников.
Измените коды конструктора:
CMyDoc: : CMyDoc
()
//====== Вспомогательные
переменные
double pi
= 4 . * atari (1.),
al = pi / 10.
, // Углы
a2 = 3. * al,
// ======
2 характерные точки
x1 = cos (al) ,
yl = sin (al) ,
x2 = cos (a2) ,
y2 = sin(a2) ,
x[5], у [5];
//===== Вещественные
(World) координаты углов звезды
//===== Считаем, что начало координат находится
//===== в геометрическом центре звезды
х [ 0 ] = 0 . ; у [ 0 ] = 1 . ; // Макушка звезды
х[1] = -х2; у[1] = -у2; // Нижний левый угол
х[2] = xl; У [2] = yl; // Верхний правый угол
х[3] = -xl; y[3] = yl; // Верхний левый угол
х[4] = х2; У
[4] = -у2; // Нижний правый угол
//===== Логические координаты углов звезды
//===== запоминаем в контейнере
for (int
i=0; i<5; i++)
//===== Точка в логической системе координат
// Увеличиваем в 100 раз, переводим в целые
// и сдвигаем
CPoint pt(200 + int(100. * x[i]), 150 - int(100. * y[i]));
//===== Записываем в конец контейнера
m_Points.push_back(pt);
}
}
Рисование в контексте устройства
Сейчас важно
понять следующее. Пример моделирует ситуацию, когда мы имеем реальные World-координаты
(термин, принятый в GDI) какого-то объекта, например разрез корабля, и хотим
начертить его детали, которые всегда можно аппроксимировать многоугольниками
в некоторой логической системе координат (например, листе ватмана размером 1000x1000
мм). При этом мы преобразуем реальные вещественные координаты корабля в логические,
то есть целого типа, так как мы не можем чертить с погрешностью менее 1 мм.
В соответствии с концепцией рисования в контексте устройства именно эти (логические)
координаты мы и должны использовать в функциях рисования. При последующем выводе
рисунка на экран или принтер операционная система автоматически преобразовывает
каждую его точку в аппаратные (device) координаты, зависящие от типа и возможностей
устройства вывода. Таким образом, мы имеем дело с тремя системами координат
и двумя их преобразованиями.
Если
вы посмотрите справку по теме Coordinate Spaces and Transformations (Пространства
и преобразования координат), то вы увидите, что в GDI рассматриваются четыре
координатных пространства: World, Page, Device и Physical device, однако часто
можно использовать только два (Page и Device). При этом пространства World и
Page считаются одним логическим координатным пространством, а пространства Device
в Physical device — физическим. Преобразование из пространства Device в Physical
device ограничивается только подстройкой начала координат при отображении рисунка
на каком-то конкретном устройстве вывода.
Вызовите в
окно редактора функцию On Draw. Для этого снова щелкните вкладку MyView.cpp
группы окон, вероятно, слева и введите изменения в соответствии со следующим
фрагментом:
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc
= GetDocument();
ASSERT_VALID(pDoc)
;
//======= Узнаем
размер контейнера точек
UINT nPoints
= pDoc->m_Points.size() ;
//======= Уходим,
если он пуст
if (InPoints)
return;
//=== Сохраняем
текущее состояние контекста
//=== (инструменты
GDI)
pDC->SaveDC
() ;
//=== Создаем
перо Windows для прорисовки контура
CPen pen (PS_SOLID,2,RGB(0,96,0));
//=== Выбираем
его в контекст устройства
pDC->SelectObject
(Spen);
//===Создаем
кисть Windows для закраски внутренности
CBrush brush
(RGB(240,255,250));
pDC->SelectObject
(&brush);
//===== Изображаем
полигон
pDC->Polygon
(spDoc->m_Poihts[0], nPoints);
//Восстанавливаем контекст (предыдущие инструменты GDI)
pDC->RestoreDC(-l);
}
Полезно откомпилировать и запустить приложение в незавершенном состоянии, так как это позволит увидеть, как проявляются ошибки и недомолвки. Процесс компиляции и сборки совместно называется построением (Build) проекта. Самым быстрым способом построить и запустить проект является ввод Ctrl+F5 и согласие с необходимостью повторения всего процесса.
Ход процесса компиляции и сборки проекта освещается и комментируется Studio.Net в окне Output. Сообщения об ошибках, выявленных на стадии построения, также выводятся в этом окне, но по завершении процесса появится диалоговое окно с сообщением о наличии ошибок. Теперь вы должны выбрать: продолжать ли компоновку или нет. Разумным выбором будет No. Теперь сообщения об ошибках в более подробном виде появляются в окне Task List, которое имеет универсальный характер, но в нашем частном случае используется для отображения ошибок компиляции. Окно имеет вид списка, который помогает идентифицировать и локализовать ошибки.
Выделив ошибку в списке, вы можете нажать F1 и получить по ней более подробную справку. В нашем случае, если не было ошибок ввода, вероятно, появятся более 10 ошибок, первую из которых приведем здесь:
error C2143: syntax error : missing ';' before '<' C:\My Projects\My\MyDoc.h(38)
Здесь мне хочется поговорить о том, как выудить из этих сообщений более или менее точное указание на истинное местоположение и причину ошибки или ошибок. Прежде всего надо психологически подготовиться к тому, что ошибки всегда и неизбежно будут преследовать вас. Если код содержит более 20 операторов, то он не может быть создан и введен без ошибок. Если же код содержит более 5000 операторов, то он всегда будет содержать их. Это почти аксиома (с долей иронии). Разработчик программного обеспечения вынужден большую часть жизни проводить за компьютером, бесконечно повторяя цепочку одних и тех же действий, которые составляют суть процесса отладки приложения. Чем спокойнее вы относитесь к своим ошибкам, тем быстрее вы с ними расправитесь.
Итак, первое сообщение говорит нам о том, что отсутствует точка с запятой перед знаком ' <' в строке 38 файла с описанием класса CMyDoc. Сделайте двойной щелчок па этом сообщении и курсор в окне MyDoc.h перейдет на строку
vector<CPoint> m_Points;
Если у вас нет опыта, то полученное сообщение вряд ли раскроет вам причину ошибки. Но есть еще много других сообщений. Так как компилятор имеет свойство неоднократно и по-разному сообщать об одной и той же ошибке, то, возможно, придется проанализировать все сообщения, прежде чем признать себя побежденным. Studio.Net помогает вам тем, что сообщения об ошибках могут быть отсортированы по различным атрибутам, и это иногда является ключом к быстрейшей их локализации.
Рис. 1.6. Окно Task List со списком ошибок
Воспользуйтесь контекстным меню окна Task List и выберите команду Sort by > Category. В результате ее выполнения на первое место попадает сообщение:
error C2238: unexpected token (s) preceding ";" ...
Это примерно значит: «Неизвестная лексема предшествует точке с запятой». Такое сообщение, на мой взгляд, значительно более точно определяет причину. Вот она: компилятор не знает, что такое vectoro. Причина: мы забыли подключить файл с заголовками библиотеки STL Это легко исправить, вставив в нужное место строки:
//====== Подключает часть определений STL
#include <vector>
//====== Задает область видимости имен STL
using namespace std;
Теперь надо
решить, куда вставить эти строки. Их можно вставить в начало файла MyDoc.h,
по это будет расточительно. Дело в том, что для подключения файлов заголовков
различных библиотек существует специальное место — файл Stdafx.h. Этот файл
(совместно с файлом StdAfx.cpp) используется для построения файла скомпилированных
заголовков My.pch (precompiled header) и файла скомпилированных типов StdAfx.obj.
Они расположены в папке Debug вашего проекта и служат для ускорения повторных компиляций файлов проекта после внесенных вами изменений, если они незначительны. Таким образом, подключаемые файлы библиотек, а они внушительны по размерам и компилируются только при необходимости. Сейчас настала такая необходимость, так как исправления затрагивают файл Stdafx.h. Вставьте две вышеуказанные строки в конец файла Stdafx.h и запустите музыку (Ctrl+F5).
Отметьте, что все файлы проекта компилируются заново, так как все файлы типа .срр имеют в качестве первой строку:
#include "stdafx.h"
Мы изменили stdafx.h, и компилятор заново проходит по всем зависящим (dependent) от него файлам. После построения и запуска изображение звезды должно появиться в клиентской области дочернего окна-рамки, то есть в окне, управляемом классом CMyView.
Рис. 1.7. Окно приложения My
Обратите внимание на характер заливки внутренних частей полигона, который принят по умолчанию. Он идентифицируется символьной константой ALTERNATING, но есть еще один вариант заливки — WINDING. Вставьте в функцию OnDraw, перед выводом полигона, строку.
pDC->SetPolyFillMode(WINDING);
и нажмите Ctrl+F5. Характер заливки изменился. Объяснение этого факта (и многих других) надо научиться искать в документации, сопровождающей Studio.Net. Дайте команду Help > Index, в окно Look for введите SetPolyFillMode и нажмите Enter. Появится окно Index Results for..., в котором следует сделать выбор между API-функцией SetPolyFillMode и одноименным методом класса CDC. Так как мы работаем с библиотекой MFC, то выбор почти всегда падает на методы классов, а не на одноименные функции API. Текст справки появится в окне Web Browser (многовато окон), и если вы действительно хотите понять алгоритм закрашивания кистью внутренних частей полигона, то вам придется немного потрудиться, даже имея хороший английский. К таким ситуациям тоже надо выработать правильное отношение. Программист должен быть кропотлив и терпелив.
Подведем итог: